﻿// ==UserScript==
// @name          Mixi Translator
// @namespace     http://code.google.com/p/mixitranslator/
// @description   Translates common phases on mixi.
// @include       http://mixi.jp/*
// ==/UserScript==

const filters = [

{
    name: "find 誕生日",
    regexp: /誕生日/g,
    txt: function(match) { return "Birthday"; }
  },
{
    name: "find 友人からの紹介文",
    regexp: /友人からの紹介文/g,
    txt: function(match) { return "Introductions from your friends"; }
  },
 
{
    name: "find もっと写真を見る",
    regexp: /もっと写真を見る/g,
    txt: function(match) { return "See more pictures"; }
  },

{
    name: "find 自己紹介",
    regexp: /自己紹介/g,
    txt: function(match) { return "About"; }
  },
   
{
    name: "find 出身地",
    regexp: /出身地/g,
    txt: function(match) { return "Home town"; }
  },
{
    name: "find 血液型",
    regexp: /血液型/g,
    txt: function(match) { return "Blood type"; }
  },
{
    name: "find 現住所",
    regexp: /現住所/g,
    txt: function(match) { return "Current location"; }
  },
  /*
  --edit account page--
  メールアドレス, Email  address
  
  新しいパスワード, New password
  確認, Verification
  mixiプレミアム, mixi premium
  携帯メールアドレス, Mobile email address
  日記・ブログの選択, Blog selected
  ※通常、ミクシィ日記を選択されることをお勧めします。, We recommend that you use the mixi blog
  日記・ブログのURL, url of blog
  RSSのURL, rss url
  メッセージお知らせメール,  new mail notification
  受け取らない not notified
  受け取る, notified
  アクセスブロック, block people
  
  -- Photo page --
 削除, delete
 メイン写真, main photo
 メイン写真にする, set as main photo
 写真をアップロードする, Upload the photograph
  
-- profile modification--
 プロフィール変更, Modify profile
名前, Name
 ニックネーム, nickname
 現住所, present address
 
 
 
 
  
  -- profile pages--
女性, female
海外アメリカ合衆国, United States
歳, years
型, type
*/
{
    name: "find 友人まで公開",
    regexp: /友人まで公開/g,
    txt: function(match) { return "Show to friends"; }
  },
{
    name: "find 所属",
    regexp: /所(.*)属/g,
    txt: function(match) { return "Company"; }
  },
  {
    name: "find 名前",
    regexp: /名(.*)前/g,
    txt: function(match) { return "Name"; }
  },
{
    name: "find 年齢",
    regexp: /年(.*)齢/g,
    txt: function(match) { return "Age"; }
  },
  
{
    name: "find 性 別",
    regexp: /性(.*)別/g,
    txt: function(match) { return "Sex"; }
  },
{
    name: "find 誕生日",
    regexp: /趣(.*)味/g,
    txt: function(match) { return "Hobbies"; }
  },
{
    name: "find 職業",
    regexp: /職(.*)業/g,
    txt: function(match) { return "Occupation"; }
  },
{
    name: "find 誕生日",
    regexp: /誕生日/g,
    txt: function(match) { return "Birthday"; }
  },
  


/* home page */
  {
    name: "find ソーシャル・ネットワーキング サービス",
    regexp: /ホーム/g,
    txt: function(match) { return "Home"; }
  },

  {
    name: "find 次回から自動的にログイン",
    regexp: /次回から自動的にログイン/g,
    txt: function(match) { return "Remember me"; }
  },
  {
    name: "find ※パスワードを忘れた方はこちら",
    regexp: /※パスワードを忘れた方はこちら/g,
    txt: function(match) { return "Forgot password?"; }
  },
{
    name: "find SSL(https)はこちら",
    regexp: /はこちら/g,
    txt: function(match) { return " secure server"; }
  },

   {
    name: "find 友人を招待",
    regexp: /友人を招待/g,
    txt: function(match) { return "Invite your friends"; }
  },
 
   {
    name: "find コミュニティ管理",
    regexp: /コミュニティ管理/g,
    txt: function(match) { return "Manage communities"; }
  },
    {
    name: "find 全てを見る",
    regexp: /全てを見る/g,
    txt: function(match) { return "View all "; }
  },
    {
    name: "find マイミクシィ管理",
    regexp: /マイミクシィ管理/g,
    txt: function(match) { return "Manage my Mixi"; }
  },
    {
    name: "find 友人を検索する",
    regexp: /友人を検索する/g,
    txt: function(match) { return "Search Friends"; }
  },

    {
    name: "find 全てを見る",
    regexp: /全てを見る/g,
    txt: function(match) { return "See all"; }
  },
    {
    name: "find コミュニティ最新書き込み",
    regexp: /コミュニティ最新書き込み/g,
    txt: function(match) { return "Recent community news"; }
  },
   {
    name: "find 携帯から最新情報をチェック",
    regexp: /携帯から最新情報をチェック/g,
    txt: function(match) { return "Get instant updates on your mobile"; }
  },
    {
    name: "find もっと読む",
    regexp: /もっと読む/g,
    txt: function(match) { return "Read more"; }
  },
    {
    name: "find プロフィール確認",
    regexp: /プロフィール確認/g,
    txt: function(match) { return "Show Profile"; }
  },

    {
    name: "find 写真を編集",
    regexp: /写真を編集/g,
    txt: function(match) { return "Edit photos"; }
  },
    {
    name: "find 新機能リリース・障害のご報告",
    regexp: /新機能リリース・障害のご報告/g,
    txt: function(match) { return "Release info"; }
  },
    {
    name: "find プレミアムサービスについてのご案内",
    regexp: /プレミアムサービスについてのご案内/g,
    txt: function(match) { return "Premium service"; }
  },
  {
    name: "find 広告掲載",
    regexp: /広告掲載/g,
    txt: function(match) { return "Advertising"; }
  },

  {
    name: "find ご利用上の注意事項について",
    regexp: /ご利用上の注意事項について/g,
    txt: function(match) { return "How to use mixi guide"; }
  },
  {
    name: "find ご利用上の注意",
    regexp: /ご利用上の注意/g,
    txt: function(match) { return "How to use"; }
  },
  {
    name: "find プライバシーポリシー",
    regexp: /プライバシーポリシー/g,
    txt: function(match) { return "Privacy Policy"; }
  },
  {
    name: "find 利用規約",
    regexp: /利用規約/g,
    txt: function(match) { return "Rules"; }
  },
  {
    name: "find 運営会社",
    regexp: /運営会社/g,
    txt: function(match) { return "Management"; }
  },
  {
    name: "find スタッフ募集",
    regexp: /スタッフ募集/g,
    txt: function(match) { return "Jobs"; }
  },
  {
    name: "find ヘルプ",
    regexp: /ヘルプ/g,
    txt: function(match) { return "Help"; }
  },
  {
    name: "find 新規登録について",
    regexp: /新規登録について/g,
    txt: function(match) { return "Registration"; }
  },
  {
    name: "find ミクシィについて",
    regexp: /ミクシィについて/g,
    txt: function(match) { return "About"; }
  },
	
  {
    name: "find mixi の使い方",
    regexp: /mixi の使い方/g,
    txt: function(match) { return "How to use mixi"; }
  },
    {
    name: "find 初めて mixi をご利用になる皆様へ",
    regexp: /初めて mixi をご利用になる皆様へ/g,
    txt: function(match) { return "Introduction to mixi"; }
  },
    {
    name: "find 詳細",
    regexp: /詳細/g,
    txt: function(match) { return "Details"; }
  },
    {
    name: "find マイミクシィ最新日記",
    regexp: /マイミクシィ最新日記/g,
    txt: function(match) { return "Your friends blogs"; }
  },
    {
    name: "find よくある質問",
    regexp: /よくある質問/g,
    txt: function(match) { return "FAQ"; }
  },
	{
    name: "find 最新の日記・おすすめレビュー",
    regexp: /最新の日記・おすすめレビュー/g,
    txt: function(match) { return "Your blog entries and reviews"; }
  },
    {
    name: "find 新着日記",
    regexp: /新着日記/g,
    txt: function(match) { return "Blogs"; }
  },
    {
    name: "find コミュニティ",
    regexp: /コミュニティ/g,
    txt: function(match) { return "Communities"; }
  },
    {
    name: "find フォト",
    regexp: /フォト/g,
    txt: function(match) { return "Photo albums"; }
  },
    {
    name: "find レビュー",
    regexp: /レビュー/g,
    txt: function(match) { return "Reviews"; }
  },
    {
    name: "find ログアウト",
    regexp: /ログアウト/g,
    txt: function(match) { return "Logout"; }
  },
    
    {
    name: "find 検索",
    regexp: /検索/g,
    txt: function(match) { return "Search"; }
  },
{
    name: "find プロフィール",
    regexp: /プロフィール/g,
    txt: function(match) { return "Profile"; }
  },   
  
    {
    name: "find 最新の日記",
    regexp: /最新の日記/g,
    txt: function(match) { return "Your blog entries"; }
  },
    {
    name: "find 最新情報",
    regexp: /最新情報/g,
    txt: function(match) { return "Your up to date information"; }
  },
    {
    name: "find 人",
    regexp: /人/g,
    txt: function(match) { return " People"; }
  },
   
  ];


/***********************************
 *  Helper functions for filters   *
 ***********************************/

function digits(s)
{
  return s.replace(/[^0-9]/g, "");
}

function alphanumerics(s)
{
  return s.replace(/[^0-9a-z]/ig, "");
}


/***********************************
 *           Link styling          *
 ***********************************/
    
/*

  You can make links generated by AutoLink look different from normal links
  by editing styleLink below and/or by setting up user style sheet rules.
  
  Example: on squarefree.com, make autolinked plain text links orange. (Firefox trunk only.)
  
    @-moz-document domain(squarefree.com) { 
      .autolink-plain-text-link { color: orange ! important; }
    }
      
*/

function styleLink(a, filter)
{
  a.style.borderBottom = "1px solid orange";
}


/***********************************
 *           Fix filters           *
 ***********************************/

function fixFilters()
{
  var i, r;
  for (i = 0; r = filters[i]; ++i) {
    // lowercase, and replace each run of non-alphanumerics with a single hyphen
    r.classNamePart = r.name.toLowerCase().replace(/[^0-9a-z]+/ig, "-");
    if(!r.regexp.global)
      alert("AutoLink filter " + r.name + " is not global! This will break stuff!");
  }
}
fixFilters();


/***********************************
 *      When and where to run      *
 ***********************************/

var moddingDOM = false;

window.addEventListener("load", init, false);
function init()
{
  document.addEventListener("DOMNodeInserted", nodeInserted, false);
  setTimeout(go, 50, document.body);
}

// This makes it work at Gmail.
// 20% performance penalty on a plain text file with a link on almost every line.
// Tiny performance penalty on pages with few automatically added links.
function nodeInserted(e)
{
  // our own modifications should not trigger this.
  // (we don't want our regular expression objects getting confused)
  // (we want better control over when we recurse)
  
  //GM_log("Inserted: " + e.target);
  
  if (!moddingDOM)
    go(e.target);
}



/***********************************
 *          DOM traversal          *
 ***********************************/


/*

  This script uses manual DOM traversal, in an iterative way without a stack!

  Advantages of snapshot XPath:
    * Much less code
    * 20-40% faster
    * May be possible to get another speed boost by including the regexp in the XPath expression - http://www.developer.com/xml/article.php/10929_3344421_3
    * All the cool people are using it
  
  Advantages of manual DOM traversal:
    * Lets us stop+continue (snapshot xpath doesn't let us)
    * Lets us modify DOM in strange ways without worrying.
    * Easier to control which elements we recurse into.

*/


// Ignore all children of these elements.
const skippedElements = { 
  trans:        true, // keeps us from screwing with existing links. keeps us from recursing to death :)
  noscript: true, // noscript has uninterpreted, unshown text children; don't waste time+sanity there.
  head:     true,
  script:   true,
  style:    true,
  textarea: true,
  label:    true,
  select:   true,
  button:   true
}

const gmail = (location.host == "gmail.google.com");

function skipChildren(node)
{
  if (node.tagName)  // !
  {
    if (skippedElements[node.tagName.toLowerCase()]) {
      return true;
    }
    
    if (gmail) {
      if (node.className == "ac") // gmail autocomplete (fake dropdown)
        return true;
      if (node.className == "ilc sxs") // invite foo to gmail (fake link/button)
        return true;
    }
  }

  return false;
}


function go(traversalRoot)
{
  var m;
  
  // Ensure we're not already in a forbidden element.
  for (m = traversalRoot; m != undefined; m = m.parentNode) {
    if (skipChildren(m)) {
      return;
    }
  }

  // work around bug, or in case previous user scripts did crazy stuff
  traversalRoot.normalize();

  function cont(n, didChildren)
  {
    var k = 0; // split work into chunks so Firefox doesn't freeze
    var q;
    
    while (n && k < 100)
    {
      ++k;
    
      // Do stuff at this node
      if (!didChildren && n.nodeType == 3) {
        if((q = runFiltersOnTextNode(n))) {
          n = q[0];

          // if there were changes, run filters again on the new text node that's here          
          if (q[1]) 
            continue;
        }
      }
  
      // Traverse to the "next" node in depth-first order

      if (!n.firstChild)
        didChildren = true;
  
      if (didChildren && n == traversalRoot)
        break;
      else if (!didChildren && n.firstChild && !skipChildren(n)) {
        n = n.firstChild;
        // didChildren is already false and should stay false
      }
      else {
        if (n.nextSibling) {
          n = n.nextSibling;
          didChildren = false;
        }
        else {
          n = n.parentNode;
          didChildren = true;
        }
      }
    } // end while
  
    if (!n) {
      //GM_log("Odd. traversalRoot was " + traversalRoot);
    }
    else if (n == traversalRoot) {
      //GM_log("Done");
      //alert("AutoLink time: " + (new Date() - timeBefore))
    }
    else {
      // Continue after 10ms.
      //GM_log("will have to continue");
      setTimeout(cont, 10, n, didChildren);
    }
    
  } // end function cont
  
  cont(traversalRoot, false);
}


/***********************************
 *         Running filters         *
 ***********************************/

// runFiltersOnTextNode
// Return: node at which to continue traversal, or |null| to mean no changes were made.

function runFiltersOnTextNode(node)
{
  // Too many variables.  Good hint that I need to split this function up :P
  var source, j, regexp, match, lastLastIndex, k, filter, href, anyChanges; // things
  var used, unused, firstUnused, lastUnused, a, parent, nextSibling; // nodes
  
  source = node.data;
  
  anyChanges = false;

  // runFiltersOnTextNode has its own do-too-much-at-once avoider thingie.
  // assumption: if there is one text node with a lot of matches,
  // it's more important to finish quickly than be transparent.
  // (e.g. plain text file FULL of links)
  // assumption: 40 * 100 = 140.
  k=0;
  
  for (j = 0; filter = filters[j]; ++j) {
    regexp = filter.regexp;
    
    if (regexp.test(source)) {

      parent = node.parentNode;
      nextSibling = node.nextSibling;

      
      regexp.lastIndex = 0;
      firstUnused = null;
      
      // Optimization from the linkify that came with Greasemonkey(?):
      // instead of splitting a text node multiple times, take advantage
      // of global regexps and substring.

      for (match = null, lastLastIndex = 0; k < 40 && (match = regexp.exec(source)); ) {
      
        // this should happen first, so RegExp.foo is still good :)
        txt = genLink(filter, match); 
        
        if (txt != null && txt != location.href) { 
          ++k;

          unused = document.createTextNode(source.substring(lastLastIndex, match.index));
          if (!anyChanges) {
            anyChanges = true;
            parent.removeChild(node);
            firstUnused = unused;
            moddingDOM = true;
          }
          parent.insertBefore(unused, nextSibling);

          used = document.createTextNode(match[0])
  
          trans = document.createElement("trans");
          //a.href = href;
          trans.title = "translated by filter: " + filter.name;
          //a.className = "autolink autolink-" + filter.classNamePart;
		  trans.innerHTML = txt;
		  
          styleLink(trans, filter);
  
 //         trans.appendChild(used);
		  
          parent.insertBefore(trans, nextSibling);
          
          lastLastIndex = regexp.lastIndex;
        }

      }

      if (anyChanges) {
        lastUnused = document.createTextNode(source.substring(lastLastIndex));
        parent.insertBefore(lastUnused, nextSibling);
        moddingDOM = false;
        return [firstUnused, true]
      }
      
      return [node, false];
    }
  }
  return null;
}

function genLink(filter, match)
{
  try {
    return filter.txt(match); 
  }
  catch(er) {
    return "data:text/plain,Error running AutoLink function for filter: " + encodeURIComponent(filter.name) + "%0A%0A" + encodeURIComponent(er);
  }
}